iT邦幫忙

2022 iThome 鐵人賽

DAY 6
0
Modern Web

開始搞懂React生態系系列 第 6

Day 06 React Hooks - 使用 Hook 的原則

  • 分享至 

  • xImage
  •  

如同 Function Component 一樣,Hook 也要是單純的 JavaScript Function

然後再加上二個必要的原則

不要在一般 JS Function 內呼叫 Hooks

所以只會在二種情境下呼叫 Hooks

  • ✅ 在 Function Component 中呼叫 Hooks
  • ✅ 在 Custom Hooks 中呼叫 Hooks

不要在條件式、迴圈或巢狀函式中呼叫 Hook

因為 React Hook 是用「順序」來記憶及對應在邏輯區塊呼叫 React Hook 的地方

如果在 Condition / Loop / Nested Function 中呼叫 React Hook,則容易發生問題

在元件內的 Top Level 呼叫 Hook 會是最好的作法

可以觀察接下來的範例解說,來理解為什麼要遵守這個原則

解說

首先,我們可以在單一的元件中使用多個 State 或 Effect Hook

function Form() {
  // 1. 使用 name state 變數
  const [name, setName] = useState('Mary');

  // 2. 使用一個 effect 來保存表單
  useEffect(function persistForm() {
    localStorage.setItem('formData', name);
  });

  // 3. 使用 surname state 變數
  const [surname, setSurname] = useState('Poppins');

  // 4. 使用一個 effect 來更新標題
  useEffect(function updateTitle() {
    document.title = name + ' ' + surname;
  });

  // ...
}

可以發現,在每一次的 Render 中 Hook 都是依照一樣的順序被呼叫

// ------------
// 第一次 render
// ------------
useState('Mary')           // 1. 用 'Mary' 來初始化 name state 變數
useEffect(persistForm)     // 2. 增加一個 effect 來保存表單
useState('Poppins')        // 3. 用 'Poppins' 來初始化 surname state 變數
useEffect(updateTitle)     // 4. 增加一個 effect 來更新標題

// -------------
// 第二次 render
// -------------
useState('Mary')           // 1. 讀取 name state 變數
useEffect(persistForm)     // 2. 替換了用來保存表單的 effect
useState('Poppins')        // 3. 讀取 surname state 變數
useEffect(updateTitle)     // 4. 替換了用來更新標題的 effect

// ...

但如果我們把一個 Hook 呼叫,放在條件式中會發生什麼事呢?

function Form() {
  // 1. 使用 name state 變數
  const [name, setName] = useState('Mary');

  // 2. 使用一個 effect 來保存表單
  // ? 違反原則 - 在條件式中使用 Hook
  if (name !== '') {
    useEffect(function persistForm() {
      localStorage.setItem('formData', name);
    });
  }
    
  // 3. 使用 surname state 變數
  const [surname, setSurname] = useState('Poppins');

  // 4. 使用一個 effect 來更新標題
  useEffect(function updateTitle() {
    document.title = name + ' ' + surname;
  });

  // ...
}

初次 Render 有呼叫對應的 Hook

但是後續 Render 時被跳過了,接下來的 Hook 順序也對應不上了

// ------------
// 第一次 render
// ------------
useState('Mary')           // 1. 用 'Mary' 來初始化 name state 變數
useEffect(persistForm)     // 2. 增加一個 effect 來保存表單
useState('Poppins')        // 3. 用 'Poppins' 來初始化 surname state 變數
useEffect(updateTitle)     // 4. 增加一個 effect 來更新標題

// -------------
// 第二次 render
// -------------
useState('Mary')           // 1. 讀取 name state 變數 
// useEffect(persistForm)  // X 這個 Hook 被跳過了!
useState('Poppins')        // X 2 (但之前是 3). 未能讀取 surname state 變數
useEffect(updateTitle)     // X 3 (但之前是 4). 未能取代 effect

// ...

在我們跳過的那個 Hook 後面,每下一個 Hook 呼叫,都會 shift 一個,導致 bug 的發生

調整判斷,符合原則

function Form() {
  // 1. 使用 name state 變數
  const [name, setName] = useState('Mary');

  // 2. 使用一個 effect 來保存表單
  useEffect(function persistForm() {
    // ? 調整判斷,把判斷放在 Hooks 內,以符合原則
    if (name !== '') {
      localStorage.setItem('formData', name);
    }
  });

  // 3. 使用 surname state 變數
  const [surname, setSurname] = useState('Poppins');

  // 4. 使用一個 effect 來更新標題
  useEffect(function updateTitle() {
    document.title = name + ' ' + surname;
  });

  // ...
}

如此一來,每次 Render 就會按照順序執行,不會有被跳過的情境發生

因此可以發現,只在元件的 Top Level 呼叫 Hook,把有分支邏輯的做法寫在 Hook 內,讓每個 Hook 都能按照順序被執行

ESLint Plugin 檢查

可以搭配使用 ESLINT 工具 eslint-plugin-react-hooks,提示我們不要破規原則

這個 plugin 已經預設在 CRA 的設定。

// Your ESLint configuration
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
    "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
  }
}

Hook 的命名原則

所有的 Hook ,無論是官方提供還是客製化的,都要以 use 開頭來命名。

use 開頭,才能讓 React 檢查它是否違反前面提到的 Hook 原則。

Next

所以有哪些 Hook 可以幫助 React 元件,使用 React 各項功能的特殊 Function,

接下來就更進一步理解每個 Hook 的用法及使用時機吧!

Reference

https://pjchender.dev/react/react-doc-hooks-into/

https://zh-hant.reactjs.org/docs/hooks-rules.html


上一篇
Day 05 React Component And Hooks
下一篇
Day 07 useState
系列文
開始搞懂React生態系30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言